iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
自我挑戰組

自己的 Leak, 自己抓(swift)系列 第 9

一個 Visitor 解決不了,那就再加一個(Decl Visitor)

  • 分享至 

  • xImage
  •  

我們先回顧昨天已經拿到了所有 ID

https://ithelp.ithome.com.tw/upload/images/20230920/201580305WYf0rnScD.png

目前的資料太過於零散且包含許多雜訊。


於是,一個 visitor 無法解決的問題。

那就~在來一層抽象(visitor)解決對吧!!?

fileprivate final class IdentifierVisitor: SyntaxVisitor {
  lazy var ids: [IdentifierExprSyntax] = []
  
  override final func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    return .skipChildren
  }
  override final func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
    ids.append(node)
    return .skipChildren
  }
}

fileprivate final class DeclVisitor: SyntaxVisitor {
  lazy var idVisitor: IdentifierVisitor = IdentifierVisitor(viewMode: .sourceAccurate)
  private lazy var subVisitors: [DeclVisitor] = []
  
  override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    append(node.members)
    return .skipChildren
  }
  
  public final func customWalk<SyntaxType>(_ node: SyntaxType) where SyntaxType: SyntaxProtocol {
      super.walk(node)
      self.idVisitor.walk(node)
  }

  private final func append<Syntax: SyntaxProtocol>(_ syntax: Syntax) {
      let visitor = DeclVisitor(viewMode: .sourceAccurate)
      self.subVisitors.append(visitor)
      visitor.customWalk(syntax)
  }
  
  var all: [DeclVisitor] {
    return [self] + subVisitors.flatMap(\.all)
  }
  
  var ids: [String] {
    return self.idVisitor.ids.map {
      $0.withoutTrivia().description
    }
  }
}

private func parse(_ code: String) -> [DeclVisitor] {
  let ast = Parser.parse(source: code)
  let visitor = DeclVisitor(viewMode: .sourceAccurate)
  visitor.customWalk(ast)
  return visitor.all
}

Test

import Foundation
import SwiftSyntax
import SwiftParser
import XCTest

final class DeclTests: XCTestCase {
  func testCode() {
    let code = """
    import Foundation
    
    let global = Out()

    class Out {
      func test() {
        print(global)
      }
    }

    func test() {
      class In {
        let a = 1
        func test(a: String) {
          let inner = In()
          print(self.a, a, 123)
          func test2() {
            escape {
              nonescape {
                self.test(a: "leak")
                print(inner)
              }
            }
          }
        }
      }
    }

    func escape(block: @escaping () -> Void) {}
    func nonescape(block: () -> Void) {}
    """
    
    let expect = [
      /// global
      [
        // let global = Out()
        "Out",
      ],
      /// Out
      [
        /// print(global)
        "print", "global",
      ],
      /// In
      [
        /// let inner = In()
        "In",
        /// print(self.a, a, 123)
        "print", "self", "a",
        /// escape {
        "escape",
        /// nonescape {
        "nonescape",
        ///  self.test(a: "leak")
        "self",
        ///  print(inner)
        "print", "inner",
      ]
    ]
    let result = parse(code).map(\.ids)
    XCTAssertEqual(result, expect)
  }
}

結果

最終於獲得三份 ID,並且依照 global, class In, class Out 分別蒐集而來。

https://ithelp.ithome.com.tw/upload/images/20230923/20158030oBTHcLCJRD.png


上一篇
第一個工具 ID Visitor
下一篇
第三層 Visitor (Closure Visitor)
系列文
自己的 Leak, 自己抓(swift)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言